package cn.dengta.webapp.user.service;

import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;

import cn.dengta.common.model.State;
import cn.dengta.common.model.Status;
import cn.dengta.common.util.Common;
import cn.dengta.context.model.Language;
import cn.dengta.context.model.WesKit;
import cn.dengta.context.web.RequestContextImpl;
import cn.dengta.webapp.base.service.BaseServiceImpl;
import cn.dengta.webapp.user.dao.TagDao;
import cn.dengta.webapp.user.entity.Tag;
import cn.dengta.webapp.user.entity.User;
import cn.dengta.webapp.user.model.Gender;
import cn.dengta.webapp.user.vo.TagVO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import me.codeplayer.util.*;
import org.apache.dubbo.config.annotation.DubboService;

import static cn.dengta.webapp.user.entity.Tag.*;

/**
 * 标签 服务实现类
 *
 * @author lgy
 * @since 2022-08-06
 */
@Slf4j
@DubboService
public class TagService extends BaseServiceImpl<Tag, TagDao, Long> implements RemoteTagService {

	Supplier<Map<String, List<Tag>>> tagLoader;

	Supplier<Map<String, List<TagVO>>> tagVoLoader;

	public void init() {
		loadTags(true);
		tagVoLoader(true);
	}

	/**
	 * 标签 链接 至 缓存 中（3小时有效期）
	 *
	 * @param flush 如果为true，则强制从数据库中加载最新的数据
	 * @param types 刷新指定类型
	 */
	public Map<String, List<Tag>> loadTags(boolean flush, final TagType... types) {
		Supplier<Map<String, List<Tag>>> loader = tagLoader;
		if (flush || loader == null) {
			Map<String, List<Tag>> maps = null;
			if (X.isValid(types) && loader != null) {
				maps = loader.get();
				for (TagType type : types) {
					maps.put(type.name(), find(null, type));
				}
			}
			if (maps == null) {
				tagLoader = loader = new TimeBasedCacheLoader<>(EasyDate.MILLIS_OF_HOUR * 3, () -> find(null, null).stream().collect(Collectors.groupingBy(c -> c.getType().name())));
			}
		}
		return loader.get();
	}

	public Map<String, List<TagVO>> tagVoLoader(boolean flush, final TagType... types) {
		Supplier<Map<String, List<TagVO>>> loader = tagVoLoader;
		if (flush || loader == null) { // 缓存60分钟
			tagVoLoader = loader = new TimeBasedCacheLoader<>(EasyDate.MILLIS_OF_HOUR, () -> packTagVo(flush, types));
			log.info("标签缓存刷新");
		}
		return loader.get();
	}

	public Map<String, List<TagVO>> getVoLoader(boolean flush, final TagType... types) {
		return getByTypes(tagVoLoader(flush), null, types);
	}

	public Map<Long, TagVO> findByTagTypeGenderAll(TagType type) {
		Map<String, List<TagVO>> loader = tagVoLoader(false, type);
		return CollectionUtil.toHashMap(loader.get(type.name()), TagVO::getId);
	}

	public String findByTagTypeToStr(Language language, TagType type, SubTagType subtype, Integer count, String ids) {
		if (StringUtil.isEmpty(ids)) {
			return null;
		}
		return findByTagTypeToStr(language, findByTagTypeGenderAll(type), subtype, count, ids);
	}

	public Map<Long, String> findByTagTypeToStr(Language language, TagType type, int count, Map<Long, String> tagIds) {
		if (!X.isValid(tagIds) || count <= 0) {
			return Collections.emptyMap();
		}
		final Map<Long, String> tagMap = new HashMap<>(X.size(tagIds), 1);
		final Map<Long, TagVO> voMap = findByTagTypeGenderAll(type);
		for (Map.Entry<Long, String> entry : tagIds.entrySet()) {
			tagMap.put(entry.getKey(), findByTagTypeToStr(language, voMap, null, count, entry.getValue()));
		}
		return tagMap;
	}

	public String findByTagTypeToStr(Language language, Map<Long, TagVO> tagVOMap, SubTagType subtype, Integer count, String ids) {
		language = language == null ? Language.zh_CN : language;
		if (!X.isValid(tagVOMap) || StringUtil.isEmpty(ids)) {
			return null;
		}
		final List<Long> likeTagList = Common.splitAsLongList(ids);
		if (!X.isValid(likeTagList)) {
			return null;
		}
		boolean all = count == null;
		int tc = 0;
		StringBuilder tag = new StringBuilder(100);
		for (Long id : likeTagList) {
			TagVO vo = tagVOMap.get(id);
			if (vo == null) {
				continue;
			}
			if (subtype != null && !subtype.eq(vo.getSubType())) {
				continue;
			}
			if (!tag.isEmpty()) {
				tag.append(Common.SEP);
			}
			tag.append(vo.i18nName(language));
			if (!all) {
				tc++;
				if (count == tc) {
					return tag.toString();
				}
			}
		}
		return tag.toString();
	}

	public Map<String, List<TagVO>> getVoLoader(boolean flush, Gender gender, final TagType... types) {
		return getByTypes(tagVoLoader(flush), gender, types);
	}

	protected Map<String, List<TagVO>> getByTypes(Map<String, List<TagVO>> listMap, Gender gender, @Nonnull TagType[] types) {
		if (!X.isValid(listMap)) {
			return Collections.emptyMap();
		}
		final Map<String, List<TagVO>> map = new HashMap<>(listMap.size());
		String name;
		final RequestContextImpl req = RequestContextImpl.get();
		final Language language = req.sessionLanguage();
		long nowTime = req.now().getTime();
		final WesKit wesKit = req.getWesKit();
		gender = X.expectNotNull(gender, X.map(req.sessionUser(), User::getGender));
		for (TagType type : types) {
			name = type.name();
			List<TagVO> vos = listMap.get(name);
			if (X.isValid(vos)) {
				Gender finalGender = gender;
				vos = vos.stream().filter(t -> {
					boolean valid = WesKit.isWesKit(wesKit, t.getWesKits());
					return valid && t.isOpen(nowTime) && StringUtil.notEmpty(t.i18nName(language)) && (t.gender == null || t.gender == finalGender);
				}).collect(Collectors.toList());
				if (type.isRandom()) {
					Collections.shuffle(vos);
				}
				map.put(name, X.isValid(vos) ? type.limitCount > 0 ? vos.subList(0, Math.min(vos.size(), type.limitCount)) : vos : Collections.emptyList());
			}
		}
		return map;
	}

	public Map<Long, TagVO> findMapCacheByIds(TagType type, List<Long> ids) {
		if (!X.isValid(ids) || type == null) {
			return Collections.emptyMap();
		}
		List<TagVO> voList = tagVoLoader(false).get(type.name());
		Map<Long, TagVO> map = null;
		if (X.isValid(voList)) {
			map = new HashMap<>(X.size(ids));
			for (TagVO tagVO : voList) {
				if (ids.contains(tagVO.getId())) {
					map.put(tagVO.getId(), tagVO);
				}
			}
		}
		return X.isValid(map) ? map : Collections.emptyMap();
	}

	public TagVO findMapCacheById(TagType type, Long id) {
		return findMapCacheByIds(type, List.of(id)).get(id);
	}

	/**
	 * 组装VO输出
	 */
	private Map<String, List<TagVO>> packTagVo(boolean flush, final TagType... types) {
		final Map<String, List<Tag>> loadTags = loadTags(flush, types);
		Map<String, List<TagVO>> listMap = null;
		if (X.isValid(loadTags)) {
			listMap = new LinkedHashMap<>(loadTags.size());
			for (Map.Entry<String, List<Tag>> entries : loadTags.entrySet()) {
				final List<Tag> tags = entries.getValue();
				boolean valid = X.isValid(tags);
				List<Tag> targetList = null;
				if (valid) {
					Common.replaceListValues((targetList = new ArrayList<>(tags)), TagVO::fromFront);
				}
				listMap.put(entries.getKey(), valid ? X.castType(targetList) : Collections.emptyList());
			}
		}
		return listMap;
	}

	/**
	 * 标签查询专题列表
	 */
	public List<Tag> find(String name, TagType type) {
		QueryWrapper<Tag> wrapper = new QueryWrapper<>();
		wrapper.eq(StringUtil.notEmpty(name), NAME, name)
				.eq(type != null, TYPE, X.map(type, TagType::getValue))
				.ge(STATUS, Status.YES.value)
				.eq(STATE, State.PUBLIC);
		wrapper.orderByDesc(SORT, ID);
		return selectList(wrapper);
	}

	public Map<Long, TagVO> findCountByMoment(@Nonnull Long[] tagIds) {
		if (X.isValid(tagIds)) {
			return CollectionUtil.toHashMap(baseDao.findCountByMoment(ArrayUtil.getInSQL(null, tagIds, true, false).toString()), TagVO::getId);
		}
		return Collections.emptyMap();
	}

	public void addUseCount(Long id) {
		UpdateWrapper<Tag> uw = new UpdateWrapper<>();
		uw.setSql("use_count = use_count + 1").eq(ID, id);
		this.update(uw);
	}

}
